/*
 * This file is part of aion-emu <aion-emu.com>.
 *
 *  aion-emu is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  aion-emu is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with aion-emu. If not, see <http://www.gnu.org/licenses/>.
 */
package com.aionengine.gameserver.controllers;

import com.aionengine.gameserver.configs.main.HTMLConfig;
import com.aionengine.gameserver.configs.main.MembershipConfig;
import com.aionengine.gameserver.configs.main.SecurityConfig;
import com.aionengine.gameserver.controllers.attack.AttackUtil;
import com.aionengine.gameserver.dataholders.DataManager;
import com.aionengine.gameserver.dataholders.PlayerInitialData;
import com.aionengine.gameserver.model.DescriptionId;
import com.aionengine.gameserver.model.EmotionType;
import com.aionengine.gameserver.model.Race;
import com.aionengine.gameserver.model.TaskId;
import com.aionengine.gameserver.model.actions.PlayerMode;
import com.aionengine.gameserver.model.gameobjects.*;
import com.aionengine.gameserver.model.gameobjects.player.AbyssRank;
import com.aionengine.gameserver.model.gameobjects.player.BindPointPosition;
import com.aionengine.gameserver.model.gameobjects.player.Player;
import com.aionengine.gameserver.model.gameobjects.state.CreatureState;
import com.aionengine.gameserver.model.gameobjects.state.CreatureVisualState;
import com.aionengine.gameserver.model.house.House;
import com.aionengine.gameserver.model.skill.PlayerSkillEntry;
import com.aionengine.gameserver.model.stats.container.PlayerGameStats;
import com.aionengine.gameserver.model.summons.SummonMode;
import com.aionengine.gameserver.model.summons.UnsummonType;
import com.aionengine.gameserver.model.team2.group.PlayerFilters.ExcludePlayerFilter;
import com.aionengine.gameserver.model.templates.flypath.FlyPathEntry;
import com.aionengine.gameserver.model.templates.panels.SkillPanel;
import com.aionengine.gameserver.model.templates.quest.QuestItems;
import com.aionengine.gameserver.model.templates.stats.PlayerStatsTemplate;
import com.aionengine.gameserver.network.aion.serverpackets.*;
import com.aionengine.gameserver.network.aion.serverpackets.SM_ATTACK_STATUS.LOG;
import com.aionengine.gameserver.network.aion.serverpackets.SM_ATTACK_STATUS.TYPE;
import com.aionengine.gameserver.questEngine.QuestEngine;
import com.aionengine.gameserver.questEngine.model.QuestEnv;
import com.aionengine.gameserver.restrictions.RestrictionsManager;
import com.aionengine.gameserver.services.*;
import com.aionengine.gameserver.services.abyss.AbyssService;
import com.aionengine.gameserver.services.craft.CraftSkillUpdateService;
import com.aionengine.gameserver.services.instance.InstanceService;
import com.aionengine.gameserver.services.item.ItemService;
import com.aionengine.gameserver.services.summons.SummonsService;
import com.aionengine.gameserver.skillengine.SkillEngine;
import com.aionengine.gameserver.skillengine.model.*;
import com.aionengine.gameserver.skillengine.model.Skill.SkillMethod;
import com.aionengine.gameserver.taskmanager.tasks.PlayerMoveTaskManager;
import com.aionengine.gameserver.taskmanager.tasks.TeamEffectUpdater;
import com.aionengine.gameserver.utils.MathUtil;
import com.aionengine.gameserver.utils.PacketSendUtility;
import com.aionengine.gameserver.utils.ThreadPoolManager;
import com.aionengine.gameserver.utils.audit.AuditLogger;
import com.aionengine.gameserver.world.MapRegion;
import com.aionengine.gameserver.world.World;
import com.aionengine.gameserver.world.WorldType;
import com.aionengine.gameserver.world.geo.GeoService;
import com.aionengine.gameserver.world.zone.ZoneInstance;
import com.aionengine.gameserver.world.zone.ZoneName;
import com.aionengine.gameserver.network.aion.serverpackets.SM_PLAYER_INFO;
import com.aionengine.gameserver.services.FFAInstance;
import com.aionengine.gameserver.services.custom.FFAManager;
import com.aionengine.gameserver.services.custom.PVPInstanceService;
import com.aionengine.gameserver.model.geometry.Point3D;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Future;

/**
 * This class is for controlling players.
 *
 * @author -Nemesiss-, ATracer, xavier, Sarynth, RotO, xTz, KID, Sippolo
 */
public class PlayerController extends CreatureController<Player> {

    private Logger log = LoggerFactory.getLogger(PlayerController.class);
    private boolean isInShutdownProgress;
    private long lastAttackMilis = 0;
    private long lastAttackedMilis = 0;
    private int stance = 0;
    private Listener mListener;

    @Override
    public void see(VisibleObject object) {
        super.see(object);
        if (object instanceof Player) {
            Player player = (Player) object;
            if (FFAManager.isInFFA(player) || FFAInstance.isInBG(player)) {
				PacketSendUtility.sendPacket(getOwner(), new SM_PLAYER_INFO(player, getOwner().isAggroIconTo(player), true));
			}
			else
            PacketSendUtility.sendPacket(getOwner(), new SM_PLAYER_INFO(player, getOwner().isAggroIconTo(player)));
            PacketSendUtility.sendPacket(getOwner(), new SM_MOTION(player.getObjectId(), player.getMotions().getActiveMotions()));
            if (player.isInPlayerMode(PlayerMode.RIDE)) {
                PacketSendUtility.sendPacket(getOwner(), new SM_EMOTION(player, EmotionType.RIDE, 0, player.ride.getNpcId()));
            }
            if (player.getPet() != null) {
                LoggerFactory.getLogger(PlayerController.class).debug(
                        "Player " + getOwner().getName() + " sees " + object.getName() + " that has toypet");
                PacketSendUtility.sendPacket(getOwner(), new SM_PET(3, player.getPet()));
            }
            player.getEffectController().sendEffectIconsTo(getOwner());
        } else if (object instanceof Kisk) {
            Kisk kisk = ((Kisk) object);
            PacketSendUtility.sendPacket(getOwner(), new SM_NPC_INFO(kisk, getOwner()));
            if (getOwner().getRace() == kisk.getOwnerRace())
                PacketSendUtility.sendPacket(getOwner(), new SM_KISK_UPDATE(kisk));
        } else if (object instanceof Npc) {
            Npc npc = ((Npc) object);
            PacketSendUtility.sendPacket(getOwner(), new SM_NPC_INFO(npc, getOwner()));
            if (!npc.getEffectController().isEmpty())
                npc.getEffectController().sendEffectIconsTo(getOwner());
            QuestEngine.getInstance().onAtDistance(new QuestEnv(object, getOwner(), 0, 0));
        } else if (object instanceof Summon) {
            Summon npc = ((Summon) object);
            PacketSendUtility.sendPacket(getOwner(), new SM_NPC_INFO(npc, getOwner()));
            if (!npc.getEffectController().isEmpty())
                npc.getEffectController().sendEffectIconsTo(getOwner());
        } else if (object instanceof Gatherable || object instanceof StaticObject) {
            PacketSendUtility.sendPacket(getOwner(), new SM_GATHERABLE_INFO(object));
        } else if (object instanceof Pet) {
            PacketSendUtility.sendPacket(getOwner(), new SM_PET(3, (Pet) object));
        }
    }

    @Override
    public void notSee(VisibleObject object, boolean isOutOfRange) {
        super.notSee(object, isOutOfRange);
        if (object instanceof Pet) {
            PacketSendUtility.sendPacket(getOwner(), new SM_PET(4, (Pet) object));
        } else {
            PacketSendUtility.sendPacket(getOwner(), new SM_DELETE(object, isOutOfRange ? 0 : 15));
        }
    }

    public void updateNearbyQuests() {
        HashMap<Integer, Integer> nearbyQuestList = new HashMap<>();
        for (int questId : getOwner().getPosition().getMapRegion().getParent().getQuestIds()) {
            int diff = 0;
            if (questId <= 0xFFFF)
                diff = QuestService.getLevelRequirementDiff(questId, getOwner().getCommonData().getLevel());
            if (diff <= 2 && QuestService.checkStartConditions(new QuestEnv(null, getOwner(), questId, 0), false)) {
                nearbyQuestList.put(questId, diff);
            }
        }
        PacketSendUtility.sendPacket(getOwner(), new SM_NEARBY_QUESTS(nearbyQuestList));
    }

    @Override
    public void onEnterZone(ZoneInstance zone) {
        Player player = getOwner();
        if (!zone.canRide() && player.isInPlayerMode(PlayerMode.RIDE)) {
            player.unsetPlayerMode(PlayerMode.RIDE);
        }
        InstanceService.onEnterZone(player, zone);
        if (zone.getAreaTemplate().getZoneName() == null) {
            log.error("No name found for a Zone in the map " + zone.getAreaTemplate().getWorldId());
        } else {
            QuestEngine.getInstance().onEnterZone(new QuestEnv(null, player, 0, 0), zone.getAreaTemplate().getZoneName());
        }
    }

    @Override
    public void onLeaveZone(ZoneInstance zone) {
        Player player = getOwner();
        InstanceService.onLeaveZone(player, zone);
        ZoneName zoneName = zone.getAreaTemplate().getZoneName();
        if (zoneName == null) {
            log.warn("No name for zone template in " + zone.getAreaTemplate().getWorldId());
            return;
        }
        QuestEngine.getInstance().onLeaveZone(new QuestEnv(null, player, 0, 0), zoneName);
    }

    /**
     * {@inheritDoc} Should only be triggered from one place (life stats)
     */
    // TODO [AT] move
    public void onEnterWorld() {

        InstanceService.onEnterInstance(getOwner());
        FFAManager.onEnterFFA(getOwner());
        FFAInstance.onEnterFFA(getOwner());
        PVPInstanceService.onEnter(getOwner());
        if (getOwner().getPosition().getWorldMapInstance().getParent().isExceptBuff()) {
            getOwner().getEffectController().removeAllEffects();
        }

        for (Effect ef : getOwner().getEffectController().getAbnormalEffects()) {
            if (ef.isDeityAvatar()) {
                // remove abyss transformation if worldtype != abyss && worldtype != balaurea
                if (getOwner().getWorldType() != WorldType.ABYSS && getOwner().getWorldType() != WorldType.BALAUREA
                        || getOwner().isInInstance()  || FFAInstance.isInBG(getOwner()) || FFAManager.isInFFA(getOwner())|| PVPInstanceService.isInPVP(getOwner())) {//) {
                    ef.endEffect();
                    getOwner().getEffectController().clearEffect(ef);
                }
            } else if (ef.getSkillTemplate().getDispelCategory() == DispelCategoryType.NPC_BUFF) {
                ef.endEffect();
                getOwner().getEffectController().clearEffect(ef);
            }
        }
        
        if(FFAInstance.isInBG(getOwner())){
            getOwner().getLifeStats().increaseHp(TYPE.HP, getOwner().getLifeStats().getMaxHp() + 1);
            getOwner().getLifeStats().increaseMp(TYPE.MP, getOwner().getLifeStats().getMaxMp() + 1);
            SkillEngine.getInstance().applyEffectDirectly(18191, getOwner(), getOwner(), 8000);
            SkillEngine.getInstance().applyEffectDirectly(10380, getOwner(), getOwner(), 8000);
        }
        
        if(FFAManager.isInFFA(getOwner())){
            getOwner().getLifeStats().increaseHp(TYPE.HP, getOwner().getLifeStats().getMaxHp() + 1);
            getOwner().getLifeStats().increaseMp(TYPE.MP, getOwner().getLifeStats().getMaxMp() + 1);
            getOwner().getCommonData().setDp(0);
            SkillEngine.getInstance().applyEffectDirectly(18191, getOwner(), getOwner(), 8000);
            SkillEngine.getInstance().applyEffectDirectly(10380, getOwner(), getOwner(), 8000);
            FFAManager.rCd(getOwner());
        }
        
        if(PVPInstanceService.isInPVP(getOwner())){
            if(!getOwner().isInTeam()){
           getOwner().getLifeStats().increaseHp(TYPE.HP, getOwner().getLifeStats().getMaxHp() + 1);
           getOwner().getLifeStats().increaseMp(TYPE.MP, getOwner().getLifeStats().getMaxMp() + 1);
           SkillEngine.getInstance().applyEffectDirectly(18191, getOwner(), getOwner(), 8000);
           SkillEngine.getInstance().applyEffectDirectly(10380, getOwner(), getOwner(), 8000);
            }
            else {
                getOwner().getLifeStats().increaseHp(TYPE.HP, getOwner().getLifeStats().getMaxHp() + 1);
                getOwner().getLifeStats().increaseMp(TYPE.MP, getOwner().getLifeStats().getMaxMp() + 1);
            }
        }
    }

    // TODO [AT] move
    public void onLeaveWorld() {
        SerialKillerService.getInstance().onLeaveMap(getOwner());
        InstanceService.onLeaveInstance(getOwner());
        FFAManager.onLeaveFFA(getOwner());
    }

    public void validateLoginZone() {
        int mapId;
        float x, y, z;
        byte h;
        boolean moveToBind = false;

        BindPointPosition bind = getOwner().getBindPoint();

        if (bind != null) {
            mapId = bind.getMapId();
            x = bind.getX();
            y = bind.getY();
            z = bind.getZ();
            h = bind.getHeading();
        } else {
            PlayerInitialData.LocationData start = DataManager.PLAYER_INITIAL_DATA.getSpawnLocation(getOwner().getRace());

            mapId = start.getMapId();
            x = start.getX();
            y = start.getY();
            z = start.getZ();
            h = start.getHeading();
        }
        if (!SiegeService.getInstance().validateLoginZone(getOwner())) {
            moveToBind = true;
        } else {
            long lastOnline = getOwner().getCommonData().getLastOnline().getTime();
            long secondsOffline = (System.currentTimeMillis() / 1000) - lastOnline / 1000;
            if (secondsOffline > 10 * 60) { //Logout in no-recall zone sends you to bindpoint after 10 (??) minutes
                for (ZoneInstance zone : getOwner().getPosition().getMapRegion().getZones(getOwner())) {
                    if (!zone.canRecall()) {
                        moveToBind = true;
                        break;
                    }
                }
            }
        }

        if (moveToBind)
            World.getInstance().setPosition(getOwner(), mapId, x, y, z, h);
    }

    public void onDie(@Nonnull Creature lastAttacker, boolean showPacket) {
        Player player = this.getOwner();
        player.getController().cancelCurrentSkill();
        player.setRebirthRevive(getOwner().haveSelfRezEffect());
        showPacket = player.hasResurrectBase() ? false : showPacket;
        Creature master = lastAttacker.getMaster();

        // High ranked kill announce
        AbyssRank ar = player.getAbyssRank();
        if (AbyssService.isOnPvpMap(player) && ar != null) {
            if (ar.getRank().getId() >= 10)
                AbyssService.rankedKillAnnounce(player);
        }

        if (DuelService.getInstance().isDueling(player.getObjectId())) {
            if (master != null && DuelService.getInstance().isDueling(player.getObjectId(), master.getObjectId())) {
                DuelService.getInstance().loseDuel(player);
                player.getEffectController().removeAbnormalEffectsByTargetSlot(SkillTargetSlot.DEBUFF);
                player.getLifeStats().setCurrentHpPercent(33);
                player.getLifeStats().setCurrentMpPercent(33);
                return;
            }
            DuelService.getInstance().loseDuel(player);
        }

        /**
         * Release summon
         */
        Summon summon = player.getSummon();
        if (summon != null) {
            SummonsService.doMode(SummonMode.RELEASE, summon, UnsummonType.UNSPECIFIED);
        }

        // setIsFlyingBeforeDead for PlayerReviveService
        if (player.isInState(CreatureState.FLYING)) {
            player.setIsFlyingBeforeDeath(true);
        }

        // ride
        player.setPlayerMode(PlayerMode.RIDE, null);
        player.unsetState(CreatureState.RESTING);
        player.unsetState(CreatureState.FLOATING_CORPSE);

        // unsetflying
        player.unsetState(CreatureState.FLYING);
        player.unsetState(CreatureState.GLIDING);
        player.setFlyState(0);

        if (player.isInInstance()) {
            if (player.getPosition().getWorldMapInstance().getInstanceHandler().onDie(player, lastAttacker)) {
                super.onDie(lastAttacker);
                return;
            }
        }

        MapRegion mapRegion = player.getPosition().getMapRegion();
        if (mapRegion != null && mapRegion.onDie(lastAttacker, getOwner())) {
            return;
        }

        this.doReward();

        if (master instanceof Npc || master == player) {
            if (player.getLevel() > 4 && !isNoDeathPenaltyInEffect())
                player.getCommonData().calculateExpLoss();
        }

        // Effects removed with super.onDie()
        super.onDie(lastAttacker);

        // send sm_emotion with DIE
        // have to be send after state is updated!
        sendDieFromCreature(lastAttacker, showPacket);

        QuestEngine.getInstance().onDie(new QuestEnv(null, player, 0, 0));

        if (player.isInGroup2()) {
            player.getPlayerGroup2().sendPacket(SM_SYSTEM_MESSAGE.STR_MSG_COMBAT_FRIENDLY_DEATH(player.getName()),
                    new ExcludePlayerFilter(player));
        }
    }

    @Override
    public void onDie(@Nonnull Creature lastAttacker) {
        this.onDie(lastAttacker, true);
    }

    public void sendDie() {
        sendDieFromCreature(getOwner(), true);
    }

    private void sendDieFromCreature(@Nonnull Creature lastAttacker, boolean showPacket) {
        Player player = this.getOwner();

        PacketSendUtility.broadcastPacket(player, new SM_EMOTION(player, EmotionType.DIE, 0,
                player.equals(lastAttacker) ? 0 : lastAttacker.getObjectId()), true);

        if (showPacket) {
            int kiskTimeRemaining = (player.getKisk() != null ? player.getKisk().getRemainingLifetime() : 0);
            PacketSendUtility.sendPacket(player, new SM_DIE(player.canUseRebirthRevive(), player.haveSelfRezItem(),
                    kiskTimeRemaining, 0, isInvader(player)));
        }

        PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_MSG_COMBAT_MY_DEATH);
    }

    private boolean isInvader(Player player) {
        if (player.getRace().equals(Race.ASMODIANS)) {
            return player.getWorldId() == 210060000;
        } else {
            return player.getWorldId() == 220050000;
        }
    }

    @Override
    public void doReward() {
        PvpService.getInstance().doReward(getOwner());
    }

    @Override
    public void onBeforeSpawn() {
        super.onBeforeSpawn();
        startProtectionActiveTask();
        if (getOwner().getIsFlyingBeforeDeath())
            getOwner().unsetState(CreatureState.FLOATING_CORPSE);
        else
            getOwner().unsetState(CreatureState.DEAD);
        getOwner().setState(CreatureState.ACTIVE);
    }

    @Override
    public void attackTarget(Creature target, int time) {

        PlayerGameStats gameStats = getOwner().getGameStats();

        if (!RestrictionsManager.canAttack(getOwner(), target))
            return;

        // Normal attack is already limited client side (ex. Press C and attacker approaches target)
        // but need a check server side too also for Z axis issue

        if (!MathUtil.isInAttackRange(getOwner(), target,
                (float) (getOwner().getGameStats().getAttackRange().getCurrent() / 1000f) + 1))
            return;

        if (!GeoService.getInstance().canSee(getOwner(), target)) {
            PacketSendUtility.sendPacket(getOwner(), SM_SYSTEM_MESSAGE.STR_ATTACK_OBSTACLE_EXIST);
            return;
        }

        if (target instanceof Npc) {
            QuestEngine.getInstance().onAttack(new QuestEnv(target, getOwner(), 0, 0));
        }

        int attackSpeed = gameStats.getAttackSpeed().getCurrent();

        long milis = System.currentTimeMillis();
        // network ping..
        if (milis - lastAttackMilis + 300 < attackSpeed) {
            // hack
            return;
        }
        lastAttackMilis = milis;

        /**
         * notify attack observers
         */
        super.attackTarget(target, time);

    }

    //public List<Point3D> Points2 = Arrays.asList(new Point3D(578, 314, 271), new Point3D(728, 230, 322), new Point3D(805, 302, 322), new Point3D(751, 422, 274), new Point3D(693, 519, 279));
    //public List<Point3D> Points2 = Arrays.asList(new Point3D(284, 521, 223), new Point3D(284, 479, 222), new Point3D(273, 501, 228), new Point3D(285, 491, 224), new Point3D(286, 509, 223));
    @Override
    public void onAttack(Creature creature, int skillId, TYPE type, int damage, boolean notifyAttack, LOG log) {
    	if (getOwner().getLifeStats().isAlreadyDead()) {
            return;
    	}
    	
    	/**if (FFAManager.isInFFA(getOwner())){  //Check Min Distance Inmortal for Spawn          
            for(Point3D p : Points2){
                if (MathUtil.getDistance(getOwner(), p.getX(), p.getY(), p.getZ()) < 2){
                   damage = 0; //Inmortal
                   }
                }
        }*/

        if (getOwner().isInvul() || getOwner().isProtectionActive()) {
            damage = 0;
        }

        cancelUseItem();
        cancelGathering();
        super.onAttack(creature, skillId, type, damage, notifyAttack, log);

        PacketSendUtility.broadcastPacket(getOwner(), new SM_ATTACK_STATUS(getOwner(), type, skillId, damage, log), true);

        if (creature instanceof Npc) {
            QuestEngine.getInstance().onAttack(new QuestEnv(creature, getOwner(), 0, 0));
        }

        lastAttackedMilis = System.currentTimeMillis();
    }

    /**
     * @param skillId
     * @param targetType
     * @param x
     * @param y
     * @param z
     */
    public void useSkill(int skillId, int targetType, float x, float y, float z, int time) {
        Player player = getOwner();

        Skill skill = SkillEngine.getInstance().getSkillFor(player, skillId, player.getTarget());

        if (skill != null) {
            if (!RestrictionsManager.canUseSkill(player, skill))
                return;

            skill.setTargetType(targetType, x, y, z);
            skill.setHitTime(time);
            skill.useSkill();
            if (mListener != null) {
                mListener.onPlayerUsedSkill(skill.getSkillId(), player);
            }
        }
    }

    /**
     * @param template
     * @param targetType
     * @param x
     * @param y
     * @param z
     * @param clientHitTime
     */
    public void useSkill(SkillTemplate template, int targetType, float x, float y, float z, int clientHitTime,
                         int skillLevel) {
        Player player = getOwner();
        Skill skill = null;
        skill = SkillEngine.getInstance().getSkillFor(player, template, player.getTarget());
        if (skill == null && player.isTransformed()) {
            SkillPanel panel = DataManager.PANEL_SKILL_DATA.getSkillPanel(player.getTransformModel().getPanelId());
            if (panel != null && panel.canUseSkill(template.getSkillId(), skillLevel)) {
                skill = SkillEngine.getInstance().getSkillFor(player, template, player.getTarget(), skillLevel);
            }
        }

        if (skill != null) {
            if (!RestrictionsManager.canUseSkill(player, skill))
                return;

            skill.setTargetType(targetType, x, y, z);
            skill.setHitTime(clientHitTime);
            skill.useSkill();
            if (mListener != null) {
                mListener.onPlayerUsedSkill(skill.getSkillId(), player);
            }
        }
    }

    @Override
    public void onMove() {
        getOwner().getObserveController().notifyMoveObservers();
        super.onMove();
    }

    @Override
    public void onStopMove() {
        PlayerMoveTaskManager.getInstance().removePlayer(getOwner());
        getOwner().getObserveController().notifyMoveObservers();
        getOwner().getMoveController().setInMove(false);
        cancelCurrentSkill();
        updateZone();
        super.onStopMove();
    }

    @Override
    public void onStartMove() {
        getOwner().getMoveController().setInMove(true);
        PlayerMoveTaskManager.getInstance().addPlayer(getOwner());
        cancelUseItem();
        cancelCurrentSkill();
        super.onStartMove();
    }

    @Override
    public void cancelCurrentSkill() {
        if (getOwner().getCastingSkill() == null) {
            return;
        }

        Player player = getOwner();
        Skill castingSkill = player.getCastingSkill();
        castingSkill.cancelCast();
        player.removeSkillCoolDown(castingSkill.getSkillTemplate().getCooldownId());
        player.setCasting(null);
        player.setNextSkillUse(0);
        if (castingSkill.getSkillMethod() == SkillMethod.CAST || castingSkill.getSkillMethod() == SkillMethod.CHARGE) {
            PacketSendUtility.broadcastPacket(player, new SM_SKILL_CANCEL(player, castingSkill.getSkillTemplate().getSkillId()), true);
            PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_SKILL_CANCELED);
        } else if (castingSkill.getSkillMethod() == SkillMethod.ITEM) {
            PacketSendUtility.sendPacket(player,
                    SM_SYSTEM_MESSAGE.STR_ITEM_CANCELED(new DescriptionId(castingSkill.getItemTemplate().getNameId())));
            player.removeItemCoolDown(castingSkill.getItemTemplate().getUseLimits().getDelayId());
            PacketSendUtility.broadcastPacket(player, new SM_ITEM_USAGE_ANIMATION(player.getObjectId(), castingSkill.getFirstTarget().getObjectId(), castingSkill.getItemObjectId(),
                    castingSkill.getItemTemplate().getTemplateId(), 0, 3, 0), true);
        }
    }

    @Override
    public void cancelUseItem() {
        Player player = getOwner();
        Item usingItem = player.getUsingItem();
        player.setUsingItem(null);
        if (hasTask(TaskId.ITEM_USE)) {
            cancelTask(TaskId.ITEM_USE);
            PacketSendUtility.broadcastPacket(player, new SM_ITEM_USAGE_ANIMATION(player.getObjectId(), usingItem == null ? 0
                    : usingItem.getObjectId(), usingItem == null ? 0 : usingItem.getItemTemplate().getTemplateId(), 0, 3, 0), true);
        }
    }

    public void cancelGathering() {
        Player player = getOwner();
        if (player.getTarget() instanceof Gatherable) {
            Gatherable g = (Gatherable) player.getTarget();
            g.getController().finishGathering(player);
        }
    }

    public void updatePassiveStats() {
        Player player = getOwner();
        for (PlayerSkillEntry skillEntry : player.getSkillList().getAllSkills()) {
            Skill skill = SkillEngine.getInstance().getSkillFor(player, skillEntry.getSkillId(), player.getTarget());
            if (skill != null && skill.isPassive()) {
                skill.useSkill();
            }
        }
    }

    @Override
    public Player getOwner() {
        return (Player) super.getOwner();
    }

    @Override
    public void onRestore(HealType healType, int value) {
        super.onRestore(healType, value);
        switch (healType) {
            case DP:
                getOwner().getCommonData().addDp(value);
                break;
		default:
			break;
        }
    }

    /**
     * @param player
     * @return
     */
    // TODO [AT] move to Player
    public boolean isDueling(Player player) {
        return DuelService.getInstance().isDueling(player.getObjectId(), getOwner().getObjectId());
    }

    // TODO [AT] rename or remove
    public boolean isInShutdownProgress() {
        return isInShutdownProgress;
    }

    // TODO [AT] rename or remove
    public void setInShutdownProgress(boolean isInShutdownProgress) {
        this.isInShutdownProgress = isInShutdownProgress;
    }

    @Override
    public void onDialogSelect(int dialogId, Player player, int questId, int extendedRewardIndex) {
        switch (dialogId) {
            case 2:
                PacketSendUtility.sendPacket(player, new SM_PRIVATE_STORE(getOwner().getStore(), player));
                break;
        }
    }

    public void upgradePlayer() {
        Player player = getOwner();
        byte level = player.getLevel();

        PlayerStatsTemplate statsTemplate = DataManager.PLAYER_STATS_DATA.getTemplate(player);
        player.setPlayerStatsTemplate(statsTemplate);

        player.getLifeStats().synchronizeWithMaxStats();
        player.getLifeStats().updateCurrentStats();

        PacketSendUtility.broadcastPacket(player, new SM_LEVEL_UPDATE(player.getObjectId(), 0, level), true);

        // Guides Html on level up
        if (HTMLConfig.ENABLE_GUIDES)
            HTMLService.sendGuideHtml(player);

        // Temporal
        ClassChangeService.showClassChangeDialog(player);

        QuestEngine.getInstance().onLvlUp(new QuestEnv(null, player, 0, 0));
        updateNearbyQuests();

        // add new skills
        SkillLearnService.addNewSkills(player);

        player.getController().updatePassiveStats();

        // add recipe for morph
        if (level == 10)
            CraftSkillUpdateService.getInstance().setMorphRecipe(player);

        if (player.isInTeam()) {
            TeamEffectUpdater.getInstance().startTask(player);
        }
        if (player.isLegionMember())
            LegionService.getInstance().updateMemberInfo(player);
        player.getNpcFactions().onLevelUp();
        PacketSendUtility.sendPacket(player, new SM_STATS_INFO(player));
    }

    /**
     * After entering game player char is "blinking" which means that it's in
     * under some protection, after making an action char stops blinking. -
     * Starts protection active - Schedules task to end protection
     */
    public void startProtectionActiveTask() {
        if (!getOwner().isProtectionActive()) {
            getOwner().setVisualState(CreatureVisualState.BLINKING);
            AttackUtil.cancelCastOn((Creature) getOwner());
            AttackUtil.removeTargetFrom((Creature) getOwner());
            PacketSendUtility.broadcastPacket(getOwner(), new SM_PLAYER_STATE(getOwner()), true);
            Future<?> task = ThreadPoolManager.getInstance().schedule(new Runnable() {
                @Override
                public void run() {
                    stopProtectionActiveTask();
                }

            }, 60000);
            addTask(TaskId.PROTECTION_ACTIVE, task);
        }
    }

    /**
     * Stops protection active task after first move or use skill
     */
    public void stopProtectionActiveTask() {
        cancelTask(TaskId.PROTECTION_ACTIVE);
        Player player = getOwner();
        if (player != null && player.isSpawned()) {
            player.unsetVisualState(CreatureVisualState.BLINKING);
            PacketSendUtility.broadcastPacket(player, new SM_PLAYER_STATE(player), true);
            notifyAIOnMove();
        }
    }

    /**
     * When player arrives at destination point of flying teleport
     */
    public void onFlyTeleportEnd() {
        Player player = getOwner();
        if (player.isInPlayerMode(PlayerMode.WINDSTREAM)) {
            player.unsetPlayerMode(PlayerMode.WINDSTREAM);
            player.getLifeStats().triggerFpReduce();
            player.unsetState(CreatureState.FLYING);
            player.setState(CreatureState.ACTIVE);
            player.setState(CreatureState.GLIDING);
            player.getGameStats().updateStatsAndSpeedVisually();
        } else {
            player.unsetState(CreatureState.FLIGHT_TELEPORT);
            player.setFlightTeleportId(0);

            if (SecurityConfig.ENABLE_FLYPATH_VALIDATOR) {
                long diff = (System.currentTimeMillis() - player.getFlyStartTime());
                FlyPathEntry path = player.getCurrentFlyPath();

                if (player.getWorldId() != path.getEndWorldId()) {
                    AuditLogger.info(player, "Player tried to use flyPath #" + path.getId() + " from not native start world "
                            + player.getWorldId() + ". expected " + path.getEndWorldId());
                }

                if (diff < path.getTimeInMs()) {
                    AuditLogger.info(player,
                            "Player " + player.getName() + " used flypath bug " + diff + " instead of " + path.getTimeInMs());
                    /*
					 * todo if works teleport player to start_* xyz, or even ban
					 */
                }

                player.setCurrentFlypath(null);
            }

            player.setFlightDistance(0);
            player.setState(CreatureState.ACTIVE);
            updateZone();
        }
    }

    public boolean addItems(int itemId, int count) {
        return ItemService.addQuestItems(getOwner(), Collections.singletonList(new QuestItems(itemId, count)));
    }

    public void startStance(final int skillId) {
        stance = skillId;
    }

    public void stopStance() {
        getOwner().getEffectController().removeEffect(stance);
        PacketSendUtility.sendPacket(getOwner(), new SM_PLAYER_STANCE(getOwner(), 0));
        stance = 0;
    }

    public int getStanceSkillId() {
        return stance;
    }

    public boolean isUnderStance() {
        return stance != 0;
    }

    public void updateSoulSickness(int skillId) {
        Player player = getOwner();
        House house = player.getActiveHouse();
        if (house != null)
            switch (house.getHouseType()) {
                case MANSION:
                case ESTATE:
                case PALACE:
                    return;
			default:
				break;
            }

        if (!player.havePermission(MembershipConfig.DISABLE_SOULSICKNESS)) {
            int deathCount = player.getCommonData().getDeathCount();
            if (deathCount < 10) {
                deathCount++;
                player.getCommonData().setDeathCount(deathCount);
            }

            if (skillId == 0)
                skillId = 8291;
            SkillEngine.getInstance().getSkill(player, skillId, deathCount, player).useSkill();
        }
    }

    /**
     * Player is considered in combat if he's been attacked or has attacked less
     * or equal 10s before
     *
     * @return true if the player is actively in combat
     */
    public boolean isInCombat() {
        return (((System.currentTimeMillis() - lastAttackedMilis) <= 10000) || ((System.currentTimeMillis() - lastAttackMilis) <= 10000));
    }

    /**
     * Check if NoDeathPenalty is active
     *
     * @param player
     * @return boolean
     */
    public boolean isNoDeathPenaltyInEffect() {
        // Check if NoDeathPenalty is active
        Iterator<Effect> iterator = getOwner().getEffectController().iterator();
        while (iterator.hasNext()) {
            Effect effect = iterator.next();
            if (effect.isNoDeathPenalty())
                return true;
        }
        return false;
    }

    /**
     * Check if NoResurrectPenalty is active
     *
     * @param player
     * @return boolean
     */
    public boolean isNoResurrectPenaltyInEffect() {
        // Check if NoResurrectPenalty is active
        Iterator<Effect> iterator = getOwner().getEffectController().iterator();
        while (iterator.hasNext()) {
            Effect effect = iterator.next();
            if (effect.isNoResurrectPenalty())
                return true;
        }
        return false;
    }

    /**
     * Check if HiPass is active
     *
     * @param player
     * @return boolean
     */
    public boolean isHiPassInEffect() {
        // Check if HiPass is active
        Iterator<Effect> iterator = getOwner().getEffectController().iterator();
        while (iterator.hasNext()) {
            Effect effect = iterator.next();
            if (effect.isHiPass())
                return true;
        }
        return false;
    }
    
    public void registerListener(Listener listener) {
        mListener = listener;
    }

    public void unregisterListener() {
        mListener = null;
    }
    
    public static interface Listener {

        void onPlayerUsedSkill(int skillId, Player player);
    }

}